/*******************************************************************************
 * Copyright (c) 2013, 2017 ACIN, fortiss GmbH
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Martin Melik-Merkumians, Alois Zoitl - initial API and implementation and/or initial documentation
 *******************************************************************************/
#include "cwin32sercomlayer.h"
#include "cwin32sercomhandler.h"
#include "forte/cominfra/commfb.h"
#include "forte/cominfra/comlayersmanager.h"

using namespace forte::literals;

namespace forte::arch {
  namespace {
    [[maybe_unused]] const forte::com_infra::ComLayerManager::EntryImpl<CWin32SerComLayer> entry("ser"_STRID);
  }

  CWin32SerComLayer::CWin32SerComLayer(forte::com_infra::CComLayer *paUpperLayer, forte::com_infra::CBaseCommFB *paFB) :
      CSerialComLayerBase(paUpperLayer, paFB) {
  }

  CWin32SerComLayer::~CWin32SerComLayer() {
    closeConnection();
  }

  forte::com_infra::EComResponse CWin32SerComLayer::recvData(const void *, unsigned int) {
    mInterruptResp = forte::com_infra::e_Nothing;

    DWORD dwBytesRead = 0;
    if (ReadFile(static_cast<HANDLE>(mSerialHandle), mRecvBuffer, mMaxRecvBuffer - 1, &dwBytesRead,
                 nullptr)) { // TODO: Failure handling and send INITO-
      if (0 < dwBytesRead) {
        mBufFillSize = dwBytesRead;
        mInterruptResp = forte::com_infra::e_ProcessDataOk;
        mFb->interruptCommFB(this);
      } else {
        mInterruptResp = forte::com_infra::e_ProcessDataRecvFaild;
      }
    }
    return mInterruptResp;
  }

  forte::com_infra::EComResponse CWin32SerComLayer::sendData(void *paData, unsigned int paSize) {
    DWORD dwBytesWritten = 0;
    char *pcData = static_cast<char *>(paData);
    unsigned int nToBeSent = paSize;
    // Send payload
    if (!WriteFile(static_cast<HANDLE>(mSerialHandle), pcData, nToBeSent, &dwBytesWritten, nullptr)) {
      return forte::com_infra::e_ProcessDataSendFailed;
    }
    if (dwBytesWritten != nToBeSent) {
      return forte::com_infra::e_ProcessDataSendFailed;
    }

    // Send termination symbol(s)
    if (!WriteFile(static_cast<HANDLE>(mSerialHandle), mTerminationSymbol,
                   static_cast<DWORD>(strlen(mTerminationSymbol)), &dwBytesWritten, nullptr)) {
      return forte::com_infra::e_ProcessDataSendFailed;
    }
    if (strlen(mTerminationSymbol) != dwBytesWritten) {
      return forte::com_infra::e_ProcessDataSendFailed;
    }

    return forte::com_infra::e_ProcessDataOk;
  }

  forte::com_infra::EComResponse
  CWin32SerComLayer::openSerialConnection(const SSerialParameters &paSerialParameters,
                                          CSerialComLayerBase<HANDLE>::TSerialHandleType *paHandleResult) {

    HANDLE serialHandle = CreateFile(paSerialParameters.interfaceName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, 0,
                                     OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

    if (INVALID_HANDLE_VALUE == serialHandle) {
      if (ERROR_FILE_NOT_FOUND == GetLastError()) {
        return forte::com_infra::e_ProcessDataNoSocket;
      }
      return forte::com_infra::e_ProcessDataInvalidObject;
    }

    DCB dcbSerialParams = DCB();

    dcbSerialParams.DCBlength = sizeof(dcbSerialParams);

    if (!GetCommState(serialHandle, &dcbSerialParams)) {
      return forte::com_infra::e_ProcessDataNoSocket;
    }

    dcbSerialParams.BaudRate = static_cast<DWORD>(paSerialParameters.baudRate);
    switch (paSerialParameters.baudRate) {
        // These are ok baud rates
      case e110: break;
      case e300: break;
      case e600: break;
      case e1200: break;
      case e2400: break;
      case e4800: break;
      case e9600: break;
      case e14400: break;
      case e19200: break;
      case e38400: break;
      case e57600: break;
      case e115200: break;
      case e128000: break;
      case e256000: break;
      // all other numbers are invalid!
      default: return forte::com_infra::e_InitInvalidId; break;
    }

    dcbSerialParams.ByteSize = paSerialParameters.byteSize;

    switch (paSerialParameters.stopBits) {
      case EForteSerialStopBits::eOneBit: dcbSerialParams.StopBits = ONESTOPBIT; break;
      case EForteSerialStopBits::eOne5Bits: dcbSerialParams.StopBits = ONE5STOPBITS; break;
      case EForteSerialStopBits::eTwoBits: dcbSerialParams.StopBits = TWOSTOPBITS; break;
    }

    switch (paSerialParameters.parity) {
      case EForteSerialParity::eNoParity: dcbSerialParams.Parity = NOPARITY; break;
      case EForteSerialParity::eODD: dcbSerialParams.Parity = ODDPARITY; break;
      case EForteSerialParity::eEven: dcbSerialParams.Parity = EVENPARITY; break;
      case EForteSerialParity::eMark: dcbSerialParams.Parity = MARKPARITY; break;
      case EForteSerialParity::eSpace: dcbSerialParams.Parity = SPACEPARITY; break;
    }

    if (!SetCommState(serialHandle, &dcbSerialParams)) {
      return forte::com_infra::e_ProcessDataNoSocket;
    }

    // Timeouts for non-blocking behaviour
    COMMTIMEOUTS timeouts = COMMTIMEOUTS();
    // Read timeouts
    timeouts.ReadIntervalTimeout = 50;
    timeouts.ReadTotalTimeoutConstant = 50;
    timeouts.ReadTotalTimeoutMultiplier = 10;
    // Write timeouts
    timeouts.WriteTotalTimeoutConstant = 50;
    timeouts.WriteTotalTimeoutMultiplier = 10;

    if (!SetCommTimeouts(serialHandle, &timeouts)) {
      return forte::com_infra::e_ProcessDataNoSocket;
    }

    switch (mFb->getComServiceType()) {
      case forte::com_infra::e_Server:
      case forte::com_infra::e_Client:
        getExtEvHandler<CWin32SerComHandler>().registerSerComLayer(this);
        mConnectionState = forte::com_infra::e_Connected;
        break;
      case forte::com_infra::e_Publisher: break;
      case forte::com_infra::e_Subscriber: break;
    }

    *paHandleResult = static_cast<void *>(serialHandle);

    return forte::com_infra::e_InitOk;
  }

  void CWin32SerComLayer::closeConnection() {
    getExtEvHandler<CWin32SerComHandler>().unregisterSerComLayer(this);
    CloseHandle(static_cast<HANDLE>(mSerialHandle));
  }
} // namespace forte::arch
